﻿using System;
using System.Collections.Generic;
using System.Net;
using BZLib.Global;
using BZLib.Flags;
using BZLib.Shots;

namespace BZLib
{
    namespace Network
    {
        // All - DataReceived classes must implement this interface.
        public interface IMessageDataReceived : IMessageData, IUnpackable
        {
        }

        public class AcceptDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.Accept ; }

            // No data received in this message

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                return str;
            }
        }
        public class AddPlayerDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.AddPlayer ; }

            // No data received in this message

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                return str;
            }
        }
        public class AdminInfoDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.AdminInfo ; }

            // Data received in this message
            public Dictionary<int, System.Net.IPAddress> Addresses { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Addresses = new Dictionary<int, IPAddress>();
                int numAddresses = str.UnpackUByte();

                for (int i = 0; i < numAddresses; i++)
                {
                    int len = str.UnpackUByte();
                    int playerID = str.UnpackUByte();
                    str.UnpackUByte(); // Unused byte, ignore
                    IPAddress address = IPAddress.Parse(String.Format("{0}.{1}.{2}.{3}",
                        str.UnpackUByte(),
                        str.UnpackUByte(),
                        str.UnpackUByte(),
                        str.UnpackUByte()));

                    Addresses.Add(playerID, address);
                }

                return str;
            }
        }
        public class AliveDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.Alive ; }

            // Data received in this message
            public int PlayerID { get; protected set; }
            public Point Position { get; protected set; }
            public double Rotation { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                PlayerID = str.UnpackUByte();
                Position = new Point(str.UnpackFloat(), str.UnpackFloat(), str.UnpackFloat());
                Rotation = str.UnpackFloat();

                return str;
            }
        }
        public class AutoPilotDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.AutoPilot ; }

            // Data received in this message
            public int PlayerID { get; protected set; }
            public bool AutoPilot { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                PlayerID = str.UnpackUByte();
                AutoPilot = (str.UnpackUByte() != 0);

                return str;
            }
        }
        public class CacheURLDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.CacheURL ; }

            // Data received in this message
            public string CacheURL { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                CacheURL = str.UnpackString(str.Length);

                return str;
            }
        }
        public class CaptureFlagDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.CaptureFlag ; }

            // Data received in this message
            public int PlayerID { get; protected set; }
            public int FlagID { get; protected set; }
            public TeamName Team { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                PlayerID = str.UnpackUByte();
                FlagID = str.UnpackUShort();
                Team = (TeamName)str.UnpackUShort();

                return str;
            }
        }
        public class ChatMessageDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.ChatMessage; }

            // Data received in this class
            public int From { get; protected set; }
            public int To { get; protected set; }
            public TeamName Team { get; protected set; }
            public string Text { get; protected set; }
            public bool Action { get; protected set; }
            public bool Server { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                From = str.UnpackUByte();
                To = str.UnpackUByte();
                Text = str.UnpackString(str.Length);

                if (To > (int)NetPlayer.LastReal)
                {
                    NetPlayer player = (NetPlayer)To;
                    if (player != NetPlayer.All)
                    {
                        if (player == NetPlayer.Server)
                            Server = true;

                        else
                            Team = Network.Utils.NetPlayerToTeamName((NetPlayer)To);
                    }
                }

                Action = Text.StartsWith(" *") && Text.EndsWith("\t*");
                if (Action)
                    Text = Text.Substring(2, Text.Length - 2);

                return str;
            }
        }
        public class CustomSoundDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.CustomSound ; }

            // Data received in this message
            public int SoundType { get; protected set; }
            public string Sound { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                SoundType = str.UnpackUShort();
                int len = (int)str.UnpackUInt();
                Sound = str.UnpackString(len);

                return str;
            }
        }
        public class DropFlagDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.DropFlag ; }

            // Data received in this message
            public int PlayerID { get; protected set; }
            public int FlagID { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                PlayerID = str.UnpackUByte();
                FlagID = str.UnpackUShort();

                return str;
            }
        }
        public class FlagUpdateReceivedData: IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.FlagUpdate; }

            // Data received in this message
            public List<Flag> Flags { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Flags = new List<Flag>();
                int flagCount = str.UnpackUShort();

                for (int i = 0; i < flagCount; i++)
                {
                    Flag flag = new Flag();
                    str = ((IUnpackable)flag).Unpack(str);
                    Flags.Add(flag);
                }

                return str;
            }
        }
        public class FetchResourcesDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.FetchResources; }

            // Data received in this message
            // int - is resource type
            // string - url of resource
            public List<KeyValuePair<int, string>> Resources { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Resources = new List<KeyValuePair<int, string>>();
                int count = str.UnpackUShort();

                for (int i = 0; i < count; i++)
                {
                    int itemType = str.UnpackUShort();
                    int urlLen = (int)str.UnpackUInt();
                    string url = str.UnpackString(urlLen);
                    Resources.Add(new KeyValuePair<int, string>(itemType, url));
                }

                return str;
            }
        }
        public class GameSettingsDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.GameSettings; }

            // Data received in this message
            public double WorldSize { get; protected set; }
            public GameStyle Style { get; protected set; }
            public int MaxPlayers { get; protected set; }
            public int MaxShots { get; protected set; }
            public int MaxFlags { get; protected set; }
            public double Speed { get; protected set; }
            public double AngVel { get; protected set; }
            public int ShakeTimeout { get; protected set; }
            public int ShakeWins { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                WorldSize = str.UnpackFloat();
                Style = (GameStyle)str.UnpackUShort();
                MaxPlayers = str.UnpackUShort();
                MaxShots = str.UnpackUShort();
                MaxFlags = str.UnpackUShort();
                Speed = str.UnpackFloat();
                AngVel = str.UnpackFloat();
                ShakeTimeout = str.UnpackUShort();
                ShakeWins = str.UnpackUShort();
                // Ignore the last unused 4 bytes

                return str;
            }
        }
        public class GameTimeDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType(){ return NetMessage.GameTime; }

            // Data received in this message
            public ulong Time { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Time = str.UnpackULong();
                return str;
            }
        }
        public class GetWorldDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.GetWorld; }

            // Data received in this message
            public string Chunk { get; protected set; }
            public int BytesLeft { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                BytesLeft = (int)str.UnpackUInt();
                Chunk = str.UnpackString(str.Length);
                return str;
            }
        }
        public class GMUpdateDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.GMUpdate; }

            // Data received in this message
            public ShotUpdate Shot { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Shot = new ShotUpdate();
                str = Shot.Unpack(str);
                return str;
            }
        }
        public class GrabFlagDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.GrabFlag; }

            // Data received in this message
            public int PlayerID { get; protected set; }
            public Flag Flag { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Flag = new Flag();
                PlayerID = str.UnpackUByte();
                str = ((IUnpackable)Flag).Unpack(str);

                return str;
            }
        }
        public class KilledDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.Killed; }

            // Data received in this class
            public int Victim { get; protected set; }
            public int Killer { get; protected set; }
            public DeathReason Reason { get; protected set; }
            public int ShotID { get; protected set; }
            public FlagType Flag { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Victim = str.UnpackUByte();
                Killer = str.UnpackUByte();
                Reason = (DeathReason)str.UnpackUShort();
                ShotID = str.UnpackUShort();
                Flag = FlagList.GetByAbbr(str.UnpackString(2));

                return str;
            }
        }
        public class LagPingDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.LagPing; }

            // No data received in this message

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                return str;
            }
        }
        public class NegotiateFlagsDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.NegotiateFlags; }
            
            // Data receieved in this message
            public List<Flag> Flags { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Flags = new List<Flag>();
                int len = str.Length / 2;

                for (int i = 0; i < len; i++)
                {
                    FlagType type = new FlagType();
                    str = FlagType.Unpack(str, ref type);

                    Flag flag = new Flag(type);
                    Flags.Add(flag);
                }

                return str;
            }
        }
        public class NewRabbitDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.NewRabbit; }

            // Data received in this message
            public int Player { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Player = str.UnpackUByte();

                return str;
            }
        }
        public class PauseDataReceieved : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.Pause; }

            // Data received in this message
            public int Player { get; protected set; }
            public bool Paused { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Player = str.UnpackUByte();
                Paused = (str.UnpackUByte() != 0);

                return str;
            }
        }
        public class PlayerInfoDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.PlayerInfo; }

            // Data received in this message
            public List<Player> Players { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                Players = new List<Player>();
                int numPlayers = str.UnpackUByte();

                for(int i = 0; i < numPlayers; i++)
                {
                    Player player = new Player();

                    player.ID = str.UnpackUByte();
                    int bits = str.UnpackUByte();
                    player.Admin = (bits & (1 << 2)) != 0;
                    player.Verified = (bits & (1 << 1)) != 0;
                    player.Registered = (bits & (1 << 0)) != 0;

                    Players.Add(player);
                }

                return str;
            }
        }
        public class PlayerUpdateDataReceivedHelper
        {
            public float Timestamp { get; protected set; }
            public byte PlayerID { get; protected set; }
            public int Order { get; protected set; }
            public int Status { get; protected set; }
            public Point Position { get; protected set; }
            public Point Velocity { get; protected set; }
            public float Rotation { get; protected set; }
            public float RotationVelocity { get; protected set; }
            public float JumpJets { get; protected set; }
            public int PhysicsDriver { get; protected set; }
            public double UserSpeed { get; protected set; }
            public double UserRotVel { get; protected set; }
            public byte Sound { get; protected set; }

            public int SmallScale { get; protected set; }

            public BinaryString UnpackBegin(BinaryString str)
            {
                SmallScale = 32766;
                
                Timestamp = str.UnpackFloat();
                PlayerID = str.UnpackUByte();
                Order = str.UnpackInt();
                Status = str.UnpackShort();

                return str;
            }

            public BinaryString UnpackEnd(BinaryString str)
            {
                if ((Status & (int)PlayerState.JumpJets) == 1)
                {
                    int jumpJetsShort = str.UnpackShort();
                    JumpJets = jumpJetsShort / SmallScale;
                }
                else
                {
                    JumpJets = 0;
                }

                if ((Status & (int)PlayerState.OnDriver) == 1)
                {
                    PhysicsDriver = str.UnpackInt();
                }
                else
                {
                    PhysicsDriver = -1;
                }

                if ((Status & (int)PlayerState.UserInputs) == 1)
                {
                    int userSpeedShort = str.UnpackShort();
                    int userRotVelShort = str.UnpackShort();

                    UserSpeed = userSpeedShort / SmallScale;
                    UserRotVel = userRotVelShort / SmallScale;
                }
                else
                {
                    UserSpeed = 0;
                    UserRotVel = 0;
                }

                if ((Status & (int)PlayerState.PlaySound) == 1)
                {
                    Sound = str.UnpackUByte();
                }
                else
                {
                    Sound = 0;
                }

                return str;
            }
        }
        public class PlayerUpdateDataReceived : PlayerUpdateDataReceivedHelper, IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.PlayerUpdate; }

            // See PlayerUpdateDataReceivedHelper for data received in this message

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                str = UnpackBegin(str);

                str.Unpack(Position);
                str.Unpack(Velocity);
                Rotation = str.UnpackFloat();
                RotationVelocity = str.UnpackFloat();

                str = UnpackEnd(str);

                return str;
            }
        }
        public class PlayerUpdateSmallDataReceived : PlayerUpdateDataReceivedHelper, IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.PlayerUpdateSmall; }

            // See PlayerUpdateDataReceivedHelper for data received in this message

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                str = UnpackBegin(str);

                Position.X = (float)str.UnpackShort() * 0.02;
                Position.Y = (float)str.UnpackShort() * 0.02;
                Position.Z = (float)str.UnpackShort() * 0.02;
                Velocity.X = (float)str.UnpackShort() * 0.01;
                Velocity.Y = (float)str.UnpackShort() * 0.01;
                Velocity.Z = (float)str.UnpackShort() * 0.01;
                Rotation = (float)((float)str.UnpackShort() * Math.PI / SmallScale);
                RotationVelocity = (float)((float)str.UnpackShort() * 0.001);

                str = UnpackEnd(str);

                return str;
            }
        }
        public class RejectDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.Reject; }

            // Data received in this message
            public int RejectCode { get; protected set; }
            public string Reason { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                RejectCode = str.UnpackUShort();
                Reason = str.UnpackString(str.Length);

                return str;
            }
        }
        public class RemovePlayerDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.RemovePlayer; }

            // Data received in this message
            public int PlayerID { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                PlayerID = str.UnpackUByte();

                return str;
            }
        }
        public class ReplayResetDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.ReplayReset; }

            // Data received in this message
            public int LastPlayer { get; protected set; } // Last non-real player in the replay

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                LastPlayer = str.UnpackUByte();

                return str;
            }
        }
        public class ScoreData
        {
            public int PlayerID;
            public int Wins;
            public int Losses;
            public int TKs;
        }
        public class ScoreDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.Score; }

            // Data received in this message
            public List<ScoreData> Scores { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                int numScores = str.UnpackUByte();

                for (int i = 0; i < numScores; i++)
                {
                    ScoreData data = new ScoreData();
                    data.PlayerID = str.UnpackUByte();
                    data.Wins = str.UnpackUShort();
                    data.Losses = str.UnpackUShort();
                    data.TKs = str.UnpackUShort();
                    Scores.Add(data);
                }

                return str;
            }
        }
        public class ScoreOverEventDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.ScoreOver; }

            // Data received in this message
            public int PlayerID { get; protected set; }
            public TeamName Team { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                PlayerID = str.UnpackUByte();
                Team = (TeamName)str.UnpackUShort();

                return str;
            }
        }
        public class SetVarEventDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.SetVar; }

            // Data received in this message
            public Dictionary<string, string> Vars { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                int numVars = str.UnpackUShort();

                for (int i = 0; i < numVars; i++)
                {
                    string name = str.UnpackString(str.UnpackUByte());
                    string value = str.UnpackString(str.UnpackUByte());
                    Vars.Add(name, value);
                }

                return str;
            }
        }
        public class ShotBeginDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.ShotBegin; }

            // Data received in this message
            public double TimeSent { get; protected set; }
            public ShotUpdate Shot { get; protected set; }
            public FlagType FlagType { get; protected set; }
            public double Lifetime { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                FiringInfo info = new FiringInfo();
                str.Unpack(info);

                TimeSent = info.TimeSent;
                Shot = info.Shot;
                FlagType = info.FlagType;
                Lifetime = info.Lifetime;

                return str;
            }
        }
        public class ShotEndDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.ShotEnd; }

            // Data received in this message
            public int PlayerID { get; protected set; }
            public int ShotID { get; protected set; }
            public int Reason { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                PlayerID = str.UnpackUByte();
                ShotID = str.UnpackShort();
                Reason = str.UnpackUShort();

                return str;
            }
        }
        public class SuperKillDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.SuperKill; }

            // No data received in this message

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                return str;
            }
        }
        public class TeamUpdateData
        {
            public TeamName Team;
            public int Size;
            public int Wins;
            public int Losses;
        }
        public class TeamUpdateDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.TeamUpdate; }

            // Data received in this message
            public Dictionary<TeamName, TeamUpdateData> Teams { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                int numTeams = str.UnpackUByte();

                for (int i = 0; i < numTeams; i++)
                {
                    TeamName team = (TeamName)str.UnpackUShort();
                    TeamUpdateData data = new TeamUpdateData();
                    data.Team = team;
                    data.Size = str.UnpackUShort();
                    data.Wins = str.UnpackUShort();
                    data.Losses = str.UnpackUShort();
                    Teams[team] = data;
                }

                return str;
            }
        }
        public class TeleportDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.Teleport; }

            // Data received in this message
            public int PlayerID { get; protected set; }
            public int From { get; protected set; }
            public int To { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                PlayerID = str.UnpackUByte();
                From = str.UnpackUShort();
                To = str.UnpackUShort();

                return str;
            }
        }
        public class TimeUpdateDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.TimeUpdate; }

            // Data received in this message
            public int TimeLeft { get; protected set; }
            public bool TimeExpired { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                TimeLeft = str.UnpackInt();
                TimeExpired = (TimeLeft == 0);

                return str;
            }
        }
        public class TransferFlagDataReceived : IMessageDataReceived
        {
            NetMessage IMessageData.GetMessageType() { return NetMessage.TransferFlag; }

            // Data received in this message
            public int From { get; protected set; }
            public int To { get; protected set; }
            public int FlagID { get; protected set; }
            public Flag Flag { get; protected set; }

            BinaryString IUnpackable.Unpack(BinaryString str)
            {
                From = str.UnpackUByte();
                To = str.UnpackUByte();
                FlagID = str.UnpackUShort();
                Flag = (Flag)str.Unpack(Flag);

                return str;
            }
        }
    }
}
